/*	____________________________________________________________________________

	         Scanner Component Implementation for the PhiMP Compiler
	____________________________________________________________________________  */


#include <iostream>
#include <fstream>
#include <string>
using namespace std;

extern ifstream sourceFile;
extern ofstream outFile, listFile;

#include "scan.h"

// *******************
// **  Constructor  **
// *******************

Scanner::Scanner()
{
	tokenBuffer = "";
	lineBuffer = "";
	lineNumber = 0;
}

// ********************************
// **  Private Member Functions  **
// ********************************

void Scanner::BufferChar(char c)
{
	if (tokenBuffer.length() < ID_STRING_LEN)
		tokenBuffer += c;
}

void Scanner::BufferString(string sz)
{
	unsigned i = 0;

	// Add string as far as we can
	while(tokenBuffer.length() < ID_STRING_LEN)
		if( i >= sz.length() )
			break;
		else
			tokenBuffer += sz.at(i++);
}

Token Scanner::CheckReserved()
{
	if( tokenBuffer == "program" ) return PROGRAM_SYM;	//==||==
	if( tokenBuffer == "int" ) return INT_TYPE;			// =||=
	if( tokenBuffer == "real" ) return REAL_TYPE;		//  ||
	if( tokenBuffer == "char" ) return CHAR_TYPE;		//  ||
	if( tokenBuffer == "var" ) return VAR_SYM;			//  ||
	if( tokenBuffer == "read" ) return READ_SYM;		//  ||
	if( tokenBuffer == "readln" ) return READLN_SYM;	// \  /
	if( tokenBuffer == "write" ) return WRITE_SYM;		//  \/
	if( tokenBuffer == "writeln" ) return WRITELN_SYM;  // 
	if( tokenBuffer == "DIV" ) return INT_DIV_OP;	    // 
	if( tokenBuffer == "MOD" ) return MOD_OP;			// C level

	if( tokenBuffer == "AND" ) return AND_OP;			//==||==
	if( tokenBuffer == "OR" ) return OR_OP;				// =||=
	if( tokenBuffer == "<=" ) return LTE_OP;			//  ||
	if( tokenBuffer == ">=" ) return GTE_OP;			//  ||
	if( tokenBuffer == "<" ) return LT_OP;				//  ||
	if( tokenBuffer == ">" ) return GT_OP;				//  ||
	if( tokenBuffer == "==" ) return EQ_OP;				//  ||
	if( tokenBuffer == "~=" ) return NE_OP;				//  ||
	if( tokenBuffer == "bool" ) return BOOL_TYPE;		//  || 
	if( tokenBuffer == "if" ) return IF_SYM;			//  ||
	if( tokenBuffer == "elif" ) return ELIF_SYM;		//  ||
	if( tokenBuffer == "else" ) return ELSE_SYM;		// \  /
	if( tokenBuffer == "true" ) return BOOL_LITERAL;	//  \/
	if( tokenBuffer == "false" ) return BOOL_LITERAL;	// B level

	if( tokenBuffer == "for" ) return FOR_SYM;			// =||=  
	if( tokenBuffer == "while" ) return WHILE_SYM;		// \  /
	if( tokenBuffer == "funct" ) return FUNCT_SYM;		//  \/
	if( tokenBuffer == "return" ) return RETURN_SYM;	//	A level

	return ID;
}

void Scanner::ClearBuffer()
{
	tokenBuffer = "";
}

void Scanner::LexicalError(char& c)
{
	cout << "\n \t\t### Oh My! ###" << endl;
	cout << " *** Lexical Error: '" << c
		<< "' ignored at position " << int(lineBuffer.size())
		<< " on line #" << lineNumber+1 << '.' << endl;
	listFile << " *** Lexical Error: '" << c
		<< "' ignored at position " << int(lineBuffer.size())
		<< " on line #" << lineNumber+1 << '.' << endl;
	c = NextChar();
}

char Scanner::NextChar()
{
	char c;

	sourceFile.get(c);
	if (c == '\n')
	{
		listFile.width(6);
		listFile << ++lineNumber << "  " << lineBuffer << endl;
		lineBuffer = "";
	}
	else
		lineBuffer += c;
	return c;
}

// *******************************
// **  Public Member Functions  **
// *******************************

Token Scanner::GetNextToken()
{
	char currentChar, c;

	ClearBuffer();
	currentChar = NextChar();
	while (!sourceFile.eof())
	{
		if (isspace(currentChar))
			currentChar = NextChar();     // do nothing
		else if (isalpha(currentChar))
		{                                // identifier
			BufferChar(currentChar);
			c = sourceFile.peek();
			while (isalnum(c) || c == '_')
			{
				currentChar = NextChar();
				BufferChar(currentChar);
				c = sourceFile.peek();
			}
			return CheckReserved();
		}
		else if (isdigit(currentChar))
		{                                // integer, real literals
			BufferChar(currentChar);
			c = sourceFile.peek();
			while (isdigit(c))
			{
				currentChar = NextChar();
				BufferChar(currentChar);
				c = sourceFile.peek();
			}
			if( c == '.' )
			{
				currentChar = NextChar();
				BufferChar(currentChar);
				c = sourceFile.peek();
				while( isdigit(c) )
				{
					currentChar = NextChar();
					BufferChar(currentChar);
					c = sourceFile.peek();
				}
				return REAL_LITERAL;
			}
			else
				return INT_LITERAL;
		}
		else if (currentChar == '(')
		{
			BufferChar(currentChar);
			return LPAREN;
		}
		else if (currentChar == '^')
		{
			BufferChar(currentChar);
			return POW_OP;
		}
		else if (currentChar == ')')
		{
			BufferChar(currentChar);
			return RPAREN;
		}
		else if (currentChar == ';')
		{
			BufferChar(currentChar);
			return SEMICOLON;
		}
		else if (currentChar == ',')
		{
			BufferChar(currentChar);
			return COMMA;
		}
		else if (currentChar == ':')
		{
			BufferChar(currentChar);
			return COLON;
		}
		else if (currentChar == '[')
		{
			BufferChar(currentChar);
			return LBRACKET;
		}
		else if (currentChar == ']')
		{
			BufferChar(currentChar);
			return RBRACKET;
		}
		else if (currentChar == '{')
		{
			BufferChar(currentChar);
			return LCURL;
		}
		else if (currentChar == '}')
		{	
			BufferChar(currentChar);
			return RCURL;
		}
		else if (currentChar == '+')
		{
			BufferChar(currentChar);
			return PLUS_OP;
		}
		else if (currentChar == '*')
		{
			BufferChar(currentChar);
			return MULT_OP;
		}
		else if (currentChar == '/')
		{
			BufferChar(currentChar);
			return DIV_OP;
		}
		else if( currentChar == '<' )
		{
			BufferChar(currentChar);
			if( sourceFile.peek() == '=' )
			{
				currentChar = NextChar();
				BufferChar(currentChar);
				return LTE_OP;
			}
			return LT_OP;
		}
		else if( currentChar == '>' )
		{
			BufferChar(currentChar);
			if( sourceFile.peek() == '=' )
			{
				currentChar = NextChar();
				BufferChar(currentChar);
				return GTE_OP;
			}
			return GT_OP;
		}
		else if( currentChar == '~' )
		{
			BufferChar(currentChar);
			if( sourceFile.peek() == '=' )
			{
				currentChar = NextChar();
				BufferChar(currentChar);
				return NE_OP;
			}
			LexicalError(currentChar);
		}
		else if( currentChar == '=' )
		{
			BufferChar(currentChar);
			if( sourceFile.peek() == '=' )
			{
				currentChar = NextChar();
				BufferChar(currentChar);
				return EQ_OP;
			}
			return ASSIGN_OP;
		}
		else if( currentChar == '-' )
		{
			BufferChar(currentChar);
			return MINUS_OP;
		}
		else if (currentChar == '#') // comment
		{
			do
				currentChar = NextChar();
			while( currentChar != '\n' );
		}
		else if (currentChar == '\'' ) // CHAR LITERAL
		{
			currentChar = NextChar();

			if( currentChar == '\n' )
				LexicalError(currentChar);

			BufferChar(currentChar);

			currentChar = NextChar();
			return CHAR_LITERAL;
		}
		else if (currentChar == '"') // STRING LITERAL
		{   
			char prevChar = ' ';
			currentChar = NextChar(); // eat the initial quote

			while( currentChar != '\n' )
			// Loop until we hit the last " or a \n (which would be a syntax error)
			{
				if( (sourceFile.peek() == '"' && currentChar != '\\') || (sourceFile.peek() == '"' && currentChar == '\\' && prevChar == '\\' ) )
				{
					BufferChar(currentChar);
					break;
				}

				BufferChar(currentChar);

				prevChar = currentChar;
				currentChar = NextChar();
			}

			currentChar = NextChar();

			if( currentChar == '\n' )
				LexicalError(currentChar);

			return STR_LITERAL;
		}
		else
			LexicalError(currentChar);
	} // end while
	return EOF_SYM;
}
